"""
插件自动更新系统
支持插件的自动检查更新和增量更新
"""

import os
import json
import hashlib
import requests
import zipfile
import tempfile
from typing import Dict, List, Optional
from dataclasses import dataclass
from datetime import datetime


@dataclass
class PluginVersion:
    """插件版本信息"""
    name: str
    version: str
    download_url: str
    checksum: str
    release_date: str
    changelog: str
    dependencies: List[str]


@dataclass
class UpdateInfo:
    """更新信息"""
    plugin_name: str
    current_version: str
    latest_version: str
    needs_update: bool
    changelog: str


class PluginUpdater:
    """插件更新器"""
    
    def __init__(self, market_url: str = "https://plugins.example.com/api"):
        self.market_url = market_url
        self._session = requests.Session()
        self._session.headers.update({
            'User-Agent': 'PluginFramework/1.0'
        })
    
    def check_for_updates(self, plugin_name: str, current_version: str) -> Optional[UpdateInfo]:
        """检查插件更新"""
        try:
            # 获取插件最新版本信息
            response = self._session.get(f"{self.market_url}/plugins/{plugin_name}")
            if response.status_code != 200:
                return None
            
            plugin_info = response.json()
            latest_version = plugin_info.get('latest_version')
            
            # 比较版本
            needs_update = self._compare_versions(current_version, latest_version)
            
            return UpdateInfo(
                plugin_name=plugin_name,
                current_version=current_version,
                latest_version=latest_version,
                needs_update=needs_update,
                changelog=plugin_info.get('changelog', '')
            )
        except Exception as e:
            print(f"Error checking updates for {plugin_name}: {e}")
            return None
    
    def _compare_versions(self, current: str, latest: str) -> bool:
        """比较版本号"""
        # 简单的版本比较实现
        try:
            current_parts = [int(x) for x in current.split('.')]
            latest_parts = [int(x) for x in latest.split('.')]
            
            for i in range(min(len(current_parts), len(latest_parts))):
                if latest_parts[i] > current_parts[i]:
                    return True
                elif latest_parts[i] < current_parts[i]:
                    return False
            
            return len(latest_parts) > len(current_parts)
        except:
            return False
    
    def download_update(self, update_info: UpdateInfo) -> Optional[str]:
        """下载更新包"""
        try:
            response = self._session.get(update_info.download_url, stream=True)
            if response.status_code != 200:
                return None
            
            # 创建临时文件
            temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip')
            temp_file.close()
            
            # 下载文件
            with open(temp_file.name, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    f.write(chunk)
            
            # 验证校验和
            if self._verify_checksum(temp_file.name, update_info.checksum):
                return temp_file.name
            else:
                os.unlink(temp_file.name)
                return None
        except Exception as e:
            print(f"Error downloading update: {e}")
            return None
    
    def _verify_checksum(self, file_path: str, expected_checksum: str) -> bool:
        """验证文件校验和"""
        try:
            with open(file_path, 'rb') as f:
                file_hash = hashlib.sha256(f.read()).hexdigest()
            return file_hash.lower() == expected_checksum.lower()
        except:
            return False
    
    def apply_update(self, plugin_name: str, update_file: str, plugin_dir: str) -> bool:
        """应用更新"""
        try:
            # 备份当前插件
            backup_dir = self._backup_plugin(plugin_name, plugin_dir)
            if not backup_dir:
                return False
            
            # 解压更新包
            with zipfile.ZipFile(update_file, 'r') as zip_ref:
                zip_ref.extractall(plugin_dir)
            
            # 清理临时文件
            os.unlink(update_file)
            
            # 验证更新
            if self._verify_update(plugin_name, plugin_dir):
                # 清理备份
                self._cleanup_backup(backup_dir)
                return True
            else:
                # 回滚
                self._rollback_update(plugin_name, backup_dir, plugin_dir)
                return False
        except Exception as e:
            print(f"Error applying update: {e}")
            return False
    
    def _backup_plugin(self, plugin_name: str, plugin_dir: str) -> Optional[str]:
        """备份插件"""
        try:
            backup_dir = tempfile.mkdtemp(prefix=f"backup_{plugin_name}_")
            backup_file = os.path.join(backup_dir, f"{plugin_name}.zip")
            
            with zipfile.ZipFile(backup_file, 'w') as zip_ref:
                for root, dirs, files in os.walk(plugin_dir):
                    for file in files:
                        file_path = os.path.join(root, file)
                        arc_path = os.path.relpath(file_path, plugin_dir)
                        zip_ref.write(file_path, arc_path)
            
            return backup_dir
        except Exception as e:
            print(f"Error backing up plugin: {e}")
            return None
    
    def _verify_update(self, plugin_name: str, plugin_dir: str) -> bool:
        """验证更新"""
        # 检查关键文件是否存在
        required_files = ['plugin.yml', '__init__.py']
        for file in required_files:
            if not os.path.exists(os.path.join(plugin_dir, file)):
                return False
        return True
    
    def _rollback_update(self, plugin_name: str, backup_dir: str, plugin_dir: str):
        """回滚更新"""
        try:
            backup_file = os.path.join(backup_dir, f"{plugin_name}.zip")
            if os.path.exists(backup_file):
                # 清理当前插件目录
                for file in os.listdir(plugin_dir):
                    file_path = os.path.join(plugin_dir, file)
                    if os.path.isfile(file_path):
                        os.unlink(file_path)
                    elif os.path.isdir(file_path):
                        import shutil
                        shutil.rmtree(file_path)
                
                # 恢复备份
                with zipfile.ZipFile(backup_file, 'r') as zip_ref:
                    zip_ref.extractall(plugin_dir)
        except Exception as e:
            print(f"Error rolling back update: {e}")
    
    def _cleanup_backup(self, backup_dir: str):
        """清理备份"""
        try:
            import shutil
            shutil.rmtree(backup_dir)
        except:
            pass


class PluginMarketClient:
    """插件市场客户端"""
    
    def __init__(self, market_url: str = "https://plugins.example.com/api"):
        self.market_url = market_url
        self.updater = PluginUpdater(market_url)
        self._session = requests.Session()
        self._session.headers.update({
            'User-Agent': 'PluginFramework/1.0'
        })
    
    def search_plugins(self, query: str = "", category: str = "") -> List[Dict]:
        """搜索插件"""
        try:
            params = {}
            if query:
                params['q'] = query
            if category:
                params['category'] = category
            
            response = self._session.get(f"{self.market_url}/plugins", params=params)
            if response.status_code == 200:
                return response.json().get('plugins', [])
        except Exception as e:
            print(f"Error searching plugins: {e}")
        
        return []
    
    def get_plugin_info(self, plugin_name: str) -> Optional[Dict]:
        """获取插件详细信息"""
        try:
            response = self._session.get(f"{self.market_url}/plugins/{plugin_name}")
            if response.status_code == 200:
                return response.json()
        except Exception as e:
            print(f"Error getting plugin info: {e}")
        
        return None
    
    def download_plugin(self, plugin_name: str, download_dir: str) -> Optional[str]:
        """下载插件"""
        try:
            plugin_info = self.get_plugin_info(plugin_name)
            if not plugin_info:
                return None
            
            download_url = plugin_info.get('download_url')
            if not download_url:
                return None
            
            response = self._session.get(download_url, stream=True)
            if response.status_code != 200:
                return None
            
            # 创建插件目录
            plugin_dir = os.path.join(download_dir, plugin_name)
            os.makedirs(plugin_dir, exist_ok=True)
            
            # 下载并解压
            temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip')
            temp_file.close()
            
            with open(temp_file.name, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    f.write(chunk)
            
            # 解压到插件目录
            with zipfile.ZipFile(temp_file.name, 'r') as zip_ref:
                zip_ref.extractall(plugin_dir)
            
            # 清理临时文件
            os.unlink(temp_file.name)
            
            return plugin_dir
        except Exception as e:
            print(f"Error downloading plugin: {e}")
            return None
    
    def get_categories(self) -> List[str]:
        """获取插件分类"""
        try:
            response = self._session.get(f"{self.market_url}/categories")
            if response.status_code == 200:
                return response.json().get('categories', [])
        except Exception as e:
            print(f"Error getting categories: {e}")
        
        return []
    
    def get_featured_plugins(self) -> List[Dict]:
        """获取推荐插件"""
        try:
            response = self._session.get(f"{self.market_url}/plugins/featured")
            if response.status_code == 200:
                return response.json().get('plugins', [])
        except Exception as e:
            print(f"Error getting featured plugins: {e}")
        
        return []